/* @flow */

/**
 * Runtime helper for resolving raw children VNodes into a slot object.
 * 示例：
 * root组件：
 * <div>
 *     <one><div slot="hello">hello</div></one>
 * </div>
 * one组件：
 * <div>
 *     <slot name="hello"></slot>
 *     <slot name="go"><div>go</div></slot>
 * </div>
 *
 * 对于上例，resolveSlots方法的第一个参数children是'<div slot="hello">hello</div>'生成
 * 的虚拟dom数组。另外one组件的$slots属性中不会出现'go'属性名的插槽。
 */
export function resolveSlots (
  children: ?Array<VNode>,
  context: ?Component
): { [key: string]: Array<VNode> } {
  const slots = {}
  if (!children) {
    return slots
  }
  const defaultSlot = []
  for (let i = 0, l = children.length; i < l; i++) {
    const child = children[i]
    // named slots should only be respected if the vnode was rendered in the
    // same context.
    // 什么时候child的context不等于context？
    // 答：当该插槽不是从直接父组件中设置，而是从直接父元素的祖先元素中传入的，然后直接父元素又
    // 将这些插槽传入当前组件中时，context就不相等。
    /**
     * 示例：
     * one：'<div><two><div slot="title">hello</div></two></div>'
     * two：'<three><slot name="title" slot="hello"></slot></three>'
     * three：'<div><slot name="hello"></slot></div>'
     * 在one组件中的内层div元素会传入到three组件中，此时该div的context属性的值为one组件的vue实例，
     * 而context变量的值为two组件的vue实例，所以此时if为false，也就是说，vue不允许实名的slot多级的传输，
     * 但是默认的slot可以跨级传输。
     * 这里可能会想着改变div的context属性指向的对象为two组件的vue实例，如下：
     * two: render(h){
     *    this.$options._renderChildren && this.$options._renderChildren.forEach((child) => {
     *         child.context = this;
     *    });
     *
     *    return h('three', this.$options._renderChildren);
     * }
     * 但是这样做有另外一个问题，因为render函数中的this不是指向two组件的vue实例，而是指向vue实例的代理对象，
     * 也就是指向在init.js中创建的vm._renderProxy属性对应的代理对象，所以会造成这里的if判断仍然为false
     * 总结来说就是，child.context === context说明当前vue实例对应的vnode的上下文必须与插槽vnode对象的上下文
     * 相同
     */
    if ((child.context === context || child.functionalContext === context) &&
      child.data && child.data.slot != null //具名插槽
    ) {
      const name = child.data.slot
      const slot = (slots[name] || (slots[name] = []))
      if (child.tag === 'template') { //可以通过template标签包裹多个插槽元素
        slot.push.apply(slot, child.children)
      } else {
        slot.push(child)
      }
    } else {
      defaultSlot.push(child)
    }
  }
  // ignore whitespace
  //只要默认插槽数组中有一个插槽不是注释或者空格，就设置默认插槽数组
  if (!defaultSlot.every(isWhitespace)) {
    slots.default = defaultSlot
  }
  return slots
}

function isWhitespace (node: VNode): boolean {
  return node.isComment || node.text === ' '
}

export function resolveScopedSlots (
  fns: ScopedSlotsData, // see flow/vnode
  res?: Object
): { [key: string]: Function } {
  res = res || {}
  for (let i = 0; i < fns.length; i++) {
    if (Array.isArray(fns[i])) {
      resolveScopedSlots(fns[i], res)
    } else {
      res[fns[i].key] = fns[i].fn
    }
  }
  return res
}
